﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Reflection;
using System.Security;
using System.Threading;

namespace PServiceBus.Serializer.Xml
{
	public static partial class Generator
	{
		internal static string ReplaceEx(this string original, string pattern, string replacement,
			StringComparison comparisonType = StringComparison.Ordinal, int stringBuilderInitialSize = -1) {
			if (original == null) {
				return null;
			}

			if (String.IsNullOrEmpty(pattern)) {
				return original;
			}


			int posCurrent = 0;
			int lenPattern = pattern.Length;
			int idxNext = original.IndexOf(pattern, comparisonType);
			StringBuilder result = new StringBuilder(stringBuilderInitialSize < 0 ? Math.Min(4096, original.Length) : stringBuilderInitialSize);

			while (idxNext >= 0) {
				result.Append(original, posCurrent, idxNext - posCurrent);
				result.Append(replacement);

				posCurrent = idxNext + lenPattern;

				idxNext = original.IndexOf(pattern, posCurrent, comparisonType);
			}

			result.Append(original, posCurrent, original.Length - posCurrent);

			return result.ToString();
		}

		public static bool IsPrimitiveType(this Type type) {
			return _primitiveTypes.GetOrAdd(type, key =>
			{
				if (key.IsGenericType &&
					key.GetGenericTypeDefinition() == _nullableType)
					key = key.GetGenericArguments()[0];

				return key == _stringType ||
					key.IsPrimitive || key == _dateTimeType ||
					key == _decimalType || key == _timeSpanType ||
					key == _guidType ||
					key.IsEnum || key == _byteArrayType;
			});
		}

		public static string GetName(this Type type) {
			var sb = new StringBuilder();
			var arguments =
				!type.IsGenericType ? Type.EmptyTypes :
				type.GetGenericArguments();
			if (!type.IsGenericType) {
				sb.Append(type.Name);
			} else {
				sb.Append(type.Name);
				foreach (var argument in arguments)
					sb.Append(GetName(argument));
			}
			return sb.ToString();
		}

		public static bool HasNilAttribute(XmlReaderEx reader) {
			return reader.TypeAttribute == NilStr;
		}

		public static void ReadToEndElement(XmlReaderEx reader, string name) {
			reader.MoveToEndElement(name);
			reader.Read();
		}

		public static void Build() { }

		public static string Fix(this string name) {
			return _fixes.GetOrAdd(name, key =>
			{
				var index = key.IndexOf(QuoteChar, StringComparison.Ordinal);
				var quoteText = index > -1 ? key.Substring(index, 2) : QuoteChar;
				var value = key.Replace(quoteText, string.Empty).Replace(ArrayLiteral, ArrayStr).Replace(AnonymousBracketStr, string.Empty);
				if (value.Contains(_quoteChr))
					value = Fix(value);
				return value;
			});
		}

		
		public static bool IsNodeValid(XmlReaderEx reader, string name) {
			return !IsNodeEnd(reader, name);
		}

		public static bool IsNodeEnd(XmlReaderEx reader, string name) {
			return reader.IsEndElement && reader.ElementName == name;
		}

		internal static object ChangeType(this string value, Type type) {
			return
				type == _stringType ? value :
				type.IsEnum ? Enum.Parse(type, value) :
				type == _byteArrayType ? Convert.FromBase64String(value) :
				type == _guidType ? (String.IsNullOrWhiteSpace(value) ? Guid.Empty : new Guid(value)) :
				type == _timeSpanType ? TimeSpan.Parse(value) :
				Convert.ChangeType(value, type);
		}

		static readonly string[] _escapeString = new[] { "&lt;", "&gt;", "&quot;", "&apos;", "&amp;" };
		static readonly string[] _unEscapeString = new string[] { "<", ">", "\"", "'", "&" };

		internal static string UnEscape(this string str) {
			for (var i = 0; i < _escapeString.Length; i++) {
				if (str.IndexOf(_escapeString[i], StringComparison.OrdinalIgnoreCase) >= 0)
					str = str.Replace(_escapeString[i], _unEscapeString[i]);
			}
			return str;
		}

		internal static string Escape(this string str) {
            var length = str.Length;
			if (str.IndexOf('<', 0, length) > 0 ||
                    str.IndexOf('>', 0, length) > 0 ||
                    str.IndexOf('\'', 0, length) > 0 ||
                    str.IndexOf('"', 0, length) > 0 ||
                    str.IndexOf('&', 0, length) > 0)
				return SecurityElement.Escape(str);
			return str;
		}

		public static StringBuilder GetStringBuilder() {
			var id = Thread.CurrentThread.ManagedThreadId;
			return _stringBuilders.GetOrAdd(id, key => new StringBuilder(DefaultStringBuilderCapacity));
		}

		public static StringBuilder WriteStartTag(this StringBuilder sb, string name) {
			return sb.Append(Less).Append(name).Append(Greater);
		}

		public static StringBuilder WriteStartWithAttributeTag(this StringBuilder sb, string name, string attribute, string attributeValue) {
			return sb.Append(Less).Append(name).Append(Space).Append(attribute).Append(Equal).Append(Quote)
				.Append(attributeValue).Append(Quote).Append(Greater);
		}

		public static StringBuilder WriteEndTag(this StringBuilder sb, string name) {
			return sb.Append(CloseTag).Append(name).Append(Greater);
		}

		public static string GetWriteString(object obj) {
			var buffer = obj as byte[];
			var str = obj as string;
			return buffer != null ? Convert.ToBase64String(buffer) :
				str != null ? str.Escape() : obj == null ? string.Empty : obj.GetType() == _intType ? IntToStr((int)obj) : obj.ToString();
		}

        public unsafe static string IntToStr(int snum) {
            char* s = stackalloc char[12];
            char* ps = s;
            int num1 = snum, num2, num3, div;
            if (snum < 0) {
                *ps++ = '-';
                //Can't negate int min
                if (snum == -2147483648)
                    return "-2147483648";
                num1 = -num1;
            }
            if (num1 < 10000) {
                if (num1 < 10) goto L1;
                if (num1 < 100) goto L2;
                if (num1 < 1000) goto L3;
            } else {
                num2 = num1 / 10000;
                num1 -= num2 * 10000;
                if (num2 < 10000) {
                    if (num2 < 10) goto L5;
                    if (num2 < 100) goto L6;
                    if (num2 < 1000) goto L7;
                } else {
                    num3 = num2 / 10000;
                    num2 -= num3 * 10000;
                    if (num3 >= 10) {
                        *ps++ = (char)('0' + (char)(div = (num3 * 6554) >> 16));
                        num3 -= div * 10;
                    }
                    *ps++ = (char)('0' + (num3));
                }
                *ps++ = (char)('0' + (div = (num2 * 8389) >> 23));
                num2 -= div * 1000;
            L7:
                *ps++ = (char)('0' + (div = (num2 * 5243) >> 19));
                num2 -= div * 100;
            L6:
                *ps++ = (char)('0' + (div = (num2 * 6554) >> 16));
                num2 -= div * 10;
            L5:
                *ps++ = (char)('0' + (num2));
            }
            *ps++ = (char)('0' + (div = (num1 * 8389) >> 23));
            num1 -= div * 1000;
        L3:
            *ps++ = (char)('0' + (div = (num1 * 5243) >> 19));
            num1 -= div * 100;
        L2:
            *ps++ = (char)('0' + (div = (num1 * 6554) >> 16));
            num1 -= div * 10;
        L1:
            *ps++ = (char)('0' + (num1));

            return new string(s);
        }

		public static void WriteObject(object obj, string name, StringBuilder writer) {
			if (obj == null) {
				writer.WriteStartWithAttributeTag(name, TypeStr, NilStr)
					.WriteEndTag(name);
			} else {
				var buffer = obj as byte[];
				var str = obj as string;
				var value = buffer != null ? Convert.ToBase64String(buffer) :
					str != null ? str.Escape() : obj.ToString();

				if (obj.GetType() == _stringType)
					writer.WriteStartWithAttributeTag(name, TypeStr, StrStr);
				else
					writer.WriteStartTag(name);
				
				writer.Append(value)
					.WriteEndTag(name);
			}
		}

		public static T ReadObject<T>(XmlReaderEx reader) {
			var value = (T)reader.GetValue().ChangeType(typeof(T));
			reader.Read();
			reader.Read();
			return value;
		}


		internal static bool IsListType(this Type type) {
			return _listType.IsAssignableFrom(type) || type.Name == IListStr;
		}

		internal static bool IsDictionaryType(this Type type) {
			return _dictType.IsAssignableFrom(type) || type.Name == IDictStr;
		}

		public static bool IsCollectionType(this Type type) {
			return type.IsListType() || type.IsDictionaryType();
		}

		public static bool IsClassType(this Type type) {
			return !type.IsCollectionType() && !type.IsPrimitiveType();
		}

		internal static IEnumerable<PropertyInfo> GetTypeProperties(this Type type) {
			return _typeProperties.GetOrAdd(type, key => key.GetProperties(PropertyBinding));
		}
	}
}
